/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// MP3 internal implementation details
// Implementation

#include "MP3InternalsHuffman.hh"

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

// This is crufty old code that needs to be cleaned up #####

static unsigned const live_tabsel[2][3][16] = {
        {
                {32, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 448},
                {32, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 384},
                {32, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 320}
        },

        {       {32, 32, 48, 56, 64,  80,  96,  112, 128, 144, 160, 176, 192, 224, 256, 256},
                {8,  8,  16, 24, 32, 40, 48, 56,  64,  80,  96,  112, 128, 144, 160, 160},
                {8,  8,  16, 24, 32, 40, 48, 56, 64,  80,  96,  112, 128, 144, 160, 160}
        }
};
/* Note: live_tabsel[*][*][0 or 15] shouldn't occur; use dummy values there */

static long const live_freqs[]
        = {44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000, 0};

struct bandInfoStruct {
    int longIdx[23];
    int longDiff[22];
    int shortIdx[14];
    int shortDiff[13];
};

static struct bandInfoStruct const bandInfo[7] = {
/* MPEG 1.0 */
        {{0, 4, 8,  12, 16, 20, 24, 30, 36, 44, 52, 62, 74,  90,  110, 134, 162, 196, 238, 288, 342, 418, 576},
                {4, 4, 4, 4, 4, 4, 6, 6,  8,  8,  10, 12, 16, 20, 24, 28, 34, 42, 50, 54, 76,  158},
                {0, 4 * 3, 8 * 3, 12 * 3, 16 * 3, 22 * 3, 30 * 3, 40 * 3, 52 * 3, 66 * 3, 84 * 3,
                        106 * 3, 136 * 3, 192 * 3},
                {4, 4, 4, 4, 6, 8,  10, 12, 14, 18, 22, 30, 56}},

        {{0, 4, 8,  12, 16, 20, 24, 30, 36, 42, 50, 60, 72,  88,  106, 128, 156, 190, 230, 276, 330, 384, 576},
                {4, 4, 4, 4, 4, 4, 6, 6,  6,  8,  10, 12, 16, 18, 22, 28, 34, 40, 46, 54, 54,  192},
                {0, 4 * 3, 8 * 3, 12 * 3, 16 * 3, 22 * 3, 28 * 3, 38 * 3, 50 * 3, 64 * 3, 80 * 3,
                        100 * 3, 126 * 3, 192 * 3},
                {4, 4, 4, 4, 6, 6,  10, 12, 14, 16, 20, 26, 66}},

        {{0, 4, 8,  12, 16, 20, 24, 30, 36, 44, 54, 66, 82,  102, 126, 156, 194, 240, 296, 364, 448, 550, 576},
                {4, 4, 4, 4, 4, 4, 6, 6,  8,  10, 12, 16, 20, 24, 30, 38, 46, 56, 68, 84, 102, 26},
                {0, 4 * 3, 8 * 3, 12 * 3, 16 * 3, 22 * 3, 30 * 3, 42 * 3, 58 * 3, 78 * 3, 104 * 3,
                        138 * 3, 180 * 3, 192 * 3},
                {4, 4, 4, 4, 6, 8,  12, 16, 20, 26, 34, 42, 12}},

/* MPEG 2.0 */
        {{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576},
                {6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 38, 46, 52, 60, 68, 58,  54},
                {0, 4 * 3, 8 * 3, 12 * 3, 18 * 3, 24 * 3, 32 * 3, 42 * 3, 56 * 3, 74 * 3, 100 * 3,
                        132 * 3, 174 * 3, 192 * 3},
                {4, 4, 4, 6, 6, 8,  10, 14, 18, 26, 32, 42, 18}},

        {{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 330, 394, 464, 540, 576},
                {6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 18, 22, 26, 32, 38, 46, 52, 64, 70, 76,  36},
                {0, 4 * 3, 8 * 3, 12 * 3, 18 * 3, 26 * 3, 36 * 3, 48 * 3, 62 * 3, 80 * 3, 104 * 3,
                        136 * 3, 180 * 3, 192 * 3},
                {4, 4, 4, 6, 8, 10, 12, 14, 18, 24, 32, 44, 12}},

        {{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576},
                {6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 38, 46, 52, 60, 68, 58,  54},
                {0, 4 * 3, 8 * 3, 12 * 3, 18 * 3, 26 * 3, 36 * 3, 48 * 3, 62 * 3, 80 * 3, 104 * 3,
                        134 * 3, 174 * 3, 192 * 3},
                {4, 4, 4, 6, 8, 10, 12, 14, 18, 24, 30, 40, 18}},

/* MPEG 2.5, wrong! table (it's just a copy of MPEG 2.0/44.1kHz) */
        {{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576},
                {6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 38, 46, 52, 60, 68, 58,  54},
                {0, 4 * 3, 8 * 3, 12 * 3, 18 * 3, 24 * 3, 32 * 3, 42 * 3, 56 * 3, 74 * 3, 100 * 3,
                        132 * 3, 174 * 3, 192 * 3},
                {4, 4, 4, 6, 6, 8,  10, 14, 18, 26, 32, 42, 18}},
};

unsigned int n_slen2[512]; /* MPEG 2.0 slen for 'normal' mode */
unsigned int i_slen2[256]; /* MPEG 2.0 slen for intensity stereo */

#define MPG_MD_MONO 3


////////// MP3FrameParams //////////

MP3FrameParams::MP3FrameParams()
        : bv(frameBytes, 0, sizeof frameBytes) /* by default */ {
    oldHdr = firstHdr = 0;

    static Boolean doneInit = False;
    if (doneInit) return;

    int i, j, k, l;

    for (i = 0; i < 5; i++) {
        for (j = 0; j < 6; j++) {
            for (k = 0; k < 6; k++) {
                int n = k + j * 6 + i * 36;
                i_slen2[n] = i | (j << 3) | (k << 6) | (3 << 12);
            }
        }
    }
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            for (k = 0; k < 4; k++) {
                int n = k + j * 4 + i * 16;
                i_slen2[n + 180] = i | (j << 3) | (k << 6) | (4 << 12);
            }
        }
    }
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 3; j++) {
            int n = j + i * 3;
            i_slen2[n + 244] = i | (j << 3) | (5 << 12);
            n_slen2[n + 500] = i | (j << 3) | (2 << 12) | (1 << 15);
        }
    }

    for (i = 0; i < 5; i++) {
        for (j = 0; j < 5; j++) {
            for (k = 0; k < 4; k++) {
                for (l = 0; l < 4; l++) {
                    int n = l + k * 4 + j * 16 + i * 80;
                    n_slen2[n] = i | (j << 3) | (k << 6) | (l << 9) | (0 << 12);
                }
            }
        }
    }
    for (i = 0; i < 5; i++) {
        for (j = 0; j < 5; j++) {
            for (k = 0; k < 4; k++) {
                int n = k + j * 4 + i * 20;
                n_slen2[n + 400] = i | (j << 3) | (k << 6) | (1 << 12);
            }
        }
    }
    doneInit = True;
}

MP3FrameParams::~MP3FrameParams() {
}

void MP3FrameParams::setParamsFromHeader() {
    if (hdr & (1 << 20)) {
        isMPEG2 = (hdr & (1 << 19)) ? 0x0 : 0x1;
        isMPEG2_5 = 0;
    } else {
        isMPEG2 = 1;
        isMPEG2_5 = 1;
    }

    layer = 4 - ((hdr >> 17) & 3);
    if (layer == 4) layer = 3; // layer==4 is not allowed
    bitrateIndex = ((hdr >> 12) & 0xf);

    if (isMPEG2_5) {
        samplingFreqIndex = ((hdr >> 10) & 0x3) + 6;
    } else {
        samplingFreqIndex = ((hdr >> 10) & 0x3) + (isMPEG2 * 3);
    }

    hasCRC = (hdr & 0x10000) == 0;

    padding = ((hdr >> 9) & 0x1);
    extension = ((hdr >> 8) & 0x1);
    mode = ((hdr >> 6) & 0x3);
    mode_ext = ((hdr >> 4) & 0x3);
    copyright = ((hdr >> 3) & 0x1);
    original = ((hdr >> 2) & 0x1);
    emphasis = hdr & 0x3;

    stereo = (mode == MPG_MD_MONO) ? 1 : 2;

    if (((hdr >> 10) & 0x3) == 0x3) {
#ifdef DEBUG_ERRORS
        fprintf(stderr,"Stream error - hdr: 0x%08x\n", hdr);
#endif
    }

    bitrate = live_tabsel[isMPEG2][layer - 1][bitrateIndex];
    samplingFreq = live_freqs[samplingFreqIndex];
    isStereo = (stereo > 1);
    isFreeFormat = (bitrateIndex == 0);
    frameSize
            = ComputeFrameSize(bitrate, samplingFreq, padding, isMPEG2, layer);
    sideInfoSize = computeSideInfoSize();
}

unsigned MP3FrameParams::computeSideInfoSize() {
    unsigned size;

    if (isMPEG2) {
        size = isStereo ? 17 : 9;
    } else {
        size = isStereo ? 32 : 17;
    }

    if (hasCRC) {
        size += 2;
    }

    return size;
}

unsigned ComputeFrameSize(unsigned bitrate, unsigned samplingFreq,
                          Boolean usePadding, Boolean isMPEG2,
                          unsigned char layer) {
    if (samplingFreq == 0) return 0;
    unsigned const bitrateMultiplier = (layer == 1) ? 12000 * 4 : 144000;
    unsigned framesize;

    framesize = bitrate * bitrateMultiplier;
    framesize /= samplingFreq << (isMPEG2 ? 1 : 0);
    framesize = framesize + usePadding - 4;

    return framesize;
}

#define TRUNC_FAIRLY

static unsigned updateSideInfoSizes(MP3SideInfo &sideInfo, Boolean isMPEG2,
                                    unsigned char const *mainDataPtr,
                                    unsigned allowedNumBits,
                                    unsigned &part23Length0a,
                                    unsigned &part23Length0aTruncation,
                                    unsigned &part23Length0b,
                                    unsigned &part23Length0bTruncation,
                                    unsigned &part23Length1a,
                                    unsigned &part23Length1aTruncation,
                                    unsigned &part23Length1b,
                                    unsigned &part23Length1bTruncation) {
    unsigned p23L0, p23L1 = 0, p23L0Trunc = 0, p23L1Trunc = 0;

    p23L0 = sideInfo.ch[0].gr[0].part2_3_length;
    p23L1 = isMPEG2 ? 0 : sideInfo.ch[0].gr[1].part2_3_length;
#ifdef TRUNC_ONLY0
    if (p23L0 < allowedNumBits)
      allowedNumBits = p23L0;
#endif
#ifdef TRUNC_ONLY1
    if (p23L1 < allowedNumBits)
      allowedNumBits = p23L1;
#endif
    if (p23L0 + p23L1 > allowedNumBits) {
        /* We need to shorten one or both fields */
        unsigned truncation = p23L0 + p23L1 - allowedNumBits;
#ifdef TRUNC_FAIRLY
        p23L0Trunc = (truncation * p23L0) / (p23L0 + p23L1);
        p23L1Trunc = truncation - p23L0Trunc;
#endif
#if defined(TRUNC_FAVOR0) || defined(TRUNC_ONLY0)
        p23L1Trunc = (truncation>p23L1) ? p23L1 : truncation;
        p23L0Trunc = truncation - p23L1Trunc;
#endif
#if defined(TRUNC_FAVOR1) || defined(TRUNC_ONLY1)
        p23L0Trunc = (truncation>p23L0) ? p23L0 : truncation;
        p23L1Trunc = truncation - p23L0Trunc;
#endif
    }

    /* ASSERT: (p23L0Trunc <= p23L0) && (p23l1Trunc <= p23L1) */
    p23L0 -= p23L0Trunc;
    p23L1 -= p23L1Trunc;
#ifdef DEBUG
    fprintf(stderr, "updateSideInfoSizes (allowed: %d): %d->%d, %d->%d\n", allowedNumBits, p23L0+p23L0Trunc, p23L0, p23L1+p23L1Trunc, p23L1);
#endif

    // The truncations computed above are still estimates.  We need to
    // adjust them so that the new fields will continue to end on
    // Huffman-encoded sample boundaries:
    updateSideInfoForHuffman(sideInfo, isMPEG2, mainDataPtr,
                             p23L0, p23L1,
                             part23Length0a, part23Length0aTruncation,
                             part23Length0b, part23Length0bTruncation,
                             part23Length1a, part23Length1aTruncation,
                             part23Length1b, part23Length1bTruncation);
    p23L0 = part23Length0a + part23Length0b;
    p23L1 = part23Length1a + part23Length1b;

    sideInfo.ch[0].gr[0].part2_3_length = p23L0;
    sideInfo.ch[0].gr[1].part2_3_length = p23L1;
    part23Length0bTruncation
            += sideInfo.ch[1].gr[0].part2_3_length; /* allow for stereo */
    sideInfo.ch[1].gr[0].part2_3_length = 0; /* output mono */
    sideInfo.ch[1].gr[1].part2_3_length = 0; /* output mono */

    return p23L0 + p23L1;
}


Boolean GetADUInfoFromMP3Frame(unsigned char const *framePtr,
                               unsigned totFrameSize,
                               unsigned &hdr, unsigned &frameSize,
                               MP3SideInfo &sideInfo, unsigned &sideInfoSize,
                               unsigned &backpointer, unsigned &aduSize) {
    if (totFrameSize < 4) return False; // there's not enough data

    MP3FrameParams fr;
    fr.hdr = ((unsigned) framePtr[0] << 24) | ((unsigned) framePtr[1] << 16)
             | ((unsigned) framePtr[2] << 8) | (unsigned) framePtr[3];
    fr.setParamsFromHeader();
    fr.setBytePointer(framePtr + 4, totFrameSize - 4); // skip hdr

    frameSize = 4 + fr.frameSize;

    if (fr.layer != 3) {
        // Special case for non-layer III frames
        backpointer = 0;
        sideInfoSize = 0;
        aduSize = fr.frameSize;
        return True;
    }

    sideInfoSize = fr.sideInfoSize;
    if (totFrameSize < 4 + sideInfoSize) return False; // not enough data

    fr.getSideInfo(sideInfo);

    hdr = fr.hdr;
    backpointer = sideInfo.main_data_begin;
    unsigned numBits = sideInfo.ch[0].gr[0].part2_3_length;
    numBits += sideInfo.ch[0].gr[1].part2_3_length;
    numBits += sideInfo.ch[1].gr[0].part2_3_length;
    numBits += sideInfo.ch[1].gr[1].part2_3_length;
    aduSize = (numBits + 7) / 8;
#ifdef DEBUG
    fprintf(stderr, "mp3GetADUInfoFromFrame: hdr: %08x, frameSize: %d, part2_3_lengths: %d,%d,%d,%d, aduSize: %d, backpointer: %d\n", hdr, frameSize, sideInfo.ch[0].gr[0].part2_3_length, sideInfo.ch[0].gr[1].part2_3_length, sideInfo.ch[1].gr[0].part2_3_length, sideInfo.ch[1].gr[1].part2_3_length, aduSize, backpointer);
#endif

    return True;
}


static void getSideInfo1(MP3FrameParams &fr, MP3SideInfo &si,
                         int stereo, int ms_stereo, long sfreq,
                         int /*single*/) {
    int ch, gr;
#if 0
    int powdiff = (single == 3) ? 4 : 0;
#endif

    /* initialize all four "part2_3_length" fields to zero: */
    si.ch[0].gr[0].part2_3_length = 0;
    si.ch[1].gr[0].part2_3_length = 0;
    si.ch[0].gr[1].part2_3_length = 0;
    si.ch[1].gr[1].part2_3_length = 0;

    si.main_data_begin = fr.getBits(9);
    if (stereo == 1)
        si.private_bits = fr.getBits(5);
    else
        si.private_bits = fr.getBits(3);

    for (ch = 0; ch < stereo; ch++) {
        si.ch[ch].gr[0].scfsi = -1;
        si.ch[ch].gr[1].scfsi = fr.getBits(4);
    }

    for (gr = 0; gr < 2; gr++) {
        for (ch = 0; ch < stereo; ch++) {
            MP3SideInfo::gr_info_s_t &gr_info = si.ch[ch].gr[gr];

            gr_info.part2_3_length = fr.getBits(12);
            gr_info.big_values = fr.getBits(9);
            gr_info.global_gain = fr.getBits(8);
#if 0
            gr_info.pow2gain = gainpow2+256 - gr_info.global_gain + powdiff;
            if (ms_stereo) gr_info.pow2gain += 2;
#endif
            gr_info.scalefac_compress = fr.getBits(4);
/* window-switching flag == 1 for block_Type != 0 .. and block-type == 0 -> win-sw-flag = 0 */
            gr_info.window_switching_flag = fr.get1Bit();
            if (gr_info.window_switching_flag) {
                int i;
                gr_info.block_type = fr.getBits(2);
                gr_info.mixed_block_flag = fr.get1Bit();
                gr_info.table_select[0] = fr.getBits(5);
                gr_info.table_select[1] = fr.getBits(5);
                /*
                 * table_select[2] not needed, because there is no region2,
                 * but to satisfy some verifications tools we set it either.
                 */
                gr_info.table_select[2] = 0;
                for (i = 0; i < 3; i++) {
                    gr_info.subblock_gain[i] = fr.getBits(3);
                    gr_info.full_gain[i]
                            = gr_info.pow2gain + ((gr_info.subblock_gain[i]) << 3);
                }

#ifdef DEBUG_ERRORS
                if (gr_info.block_type == 0) {
                  fprintf(stderr,"Blocktype == 0 and window-switching == 1 not allowed.\n");
                }
#endif
                /* region_count/start parameters are implicit in this case. */
                gr_info.region1start = 36 >> 1;
                gr_info.region2start = 576 >> 1;
            } else {
                int i, r0c, r1c;
                for (i = 0; i < 3; i++) {
                    gr_info.table_select[i] = fr.getBits(5);
                }
                r0c = gr_info.region0_count = fr.getBits(4);
                r1c = gr_info.region1_count = fr.getBits(3);
                gr_info.region1start = bandInfo[sfreq].longIdx[r0c + 1] >> 1;
                gr_info.region2start = bandInfo[sfreq].longIdx[r0c + 1 + r1c + 1] >> 1;
                gr_info.block_type = 0;
                gr_info.mixed_block_flag = 0;
            }
            gr_info.preflag = fr.get1Bit();
            gr_info.scalefac_scale = fr.get1Bit();
            gr_info.count1table_select = fr.get1Bit();
        }
    }
}

static void getSideInfo2(MP3FrameParams &fr, MP3SideInfo &si,
                         int stereo, int ms_stereo, long sfreq,
                         int /*single*/) {
    int ch;
#if 0
    int powdiff = (single == 3) ? 4 : 0;
#endif

    /* initialize all four "part2_3_length" fields to zero: */
    si.ch[0].gr[0].part2_3_length = 0;
    si.ch[1].gr[0].part2_3_length = 0;
    si.ch[0].gr[1].part2_3_length = 0;
    si.ch[1].gr[1].part2_3_length = 0;

    si.main_data_begin = fr.getBits(8);
    if (stereo == 1)
        si.private_bits = fr.get1Bit();
    else
        si.private_bits = fr.getBits(2);

    for (ch = 0; ch < stereo; ch++) {
        MP3SideInfo::gr_info_s_t &gr_info = si.ch[ch].gr[0];

        gr_info.part2_3_length = fr.getBits(12);
        si.ch[ch].gr[1].part2_3_length = 0; /* to ensure granule 1 unused */

        gr_info.big_values = fr.getBits(9);
        gr_info.global_gain = fr.getBits(8);
#if 0
        gr_info.pow2gain = gainpow2+256 - gr_info.global_gain + powdiff;
        if (ms_stereo) gr_info.pow2gain += 2;
#endif
        gr_info.scalefac_compress = fr.getBits(9);
/* window-switching flag == 1 for block_Type != 0 .. and block-type == 0 -> win-sw-flag = 0 */
        gr_info.window_switching_flag = fr.get1Bit();
        if (gr_info.window_switching_flag) {
            int i;
            gr_info.block_type = fr.getBits(2);
            gr_info.mixed_block_flag = fr.get1Bit();
            gr_info.table_select[0] = fr.getBits(5);
            gr_info.table_select[1] = fr.getBits(5);
            /*
             * table_select[2] not needed, because there is no region2,
             * but to satisfy some verifications tools we set it either.
             */
            gr_info.table_select[2] = 0;
            for (i = 0; i < 3; i++) {
                gr_info.subblock_gain[i] = fr.getBits(3);
                gr_info.full_gain[i]
                        = gr_info.pow2gain + ((gr_info.subblock_gain[i]) << 3);
            }

#ifdef DEBUG_ERRORS
            if (gr_info.block_type == 0) {
              fprintf(stderr,"Blocktype == 0 and window-switching == 1 not allowed.\n");
            }
#endif
            /* region_count/start parameters are implicit in this case. */
/* check this again! */
            if (gr_info.block_type == 2)
                gr_info.region1start = 36 >> 1;
            else {
                gr_info.region1start = 54 >> 1;
            }
            gr_info.region2start = 576 >> 1;
        } else {
            int i, r0c, r1c;
            for (i = 0; i < 3; i++) {
                gr_info.table_select[i] = fr.getBits(5);
            }
            r0c = gr_info.region0_count = fr.getBits(4);
            r1c = gr_info.region1_count = fr.getBits(3);
            gr_info.region1start = bandInfo[sfreq].longIdx[r0c + 1] >> 1;
            gr_info.region2start = bandInfo[sfreq].longIdx[r0c + 1 + r1c + 1] >> 1;
            gr_info.block_type = 0;
            gr_info.mixed_block_flag = 0;
        }
        gr_info.scalefac_scale = fr.get1Bit();
        gr_info.count1table_select = fr.get1Bit();
    }
}


#define         MPG_MD_JOINT_STEREO     1

void MP3FrameParams::getSideInfo(MP3SideInfo &si) {
    // First skip over the CRC if present:
    if (hasCRC) getBits(16);

    int single = -1;
    int ms_stereo;
    int sfreq = samplingFreqIndex;

    if (stereo == 1) {
        single = 0;
    }

    ms_stereo = (mode == MPG_MD_JOINT_STEREO) && (mode_ext & 0x2);

    if (isMPEG2) {
        getSideInfo2(*this, si, stereo, ms_stereo, sfreq, single);
    } else {
        getSideInfo1(*this, si, stereo, ms_stereo, sfreq, single);
    }
}

static void putSideInfo1(BitVector &bv,
                         MP3SideInfo const &si, Boolean isStereo) {
    int ch, gr, i;
    int stereo = isStereo ? 2 : 1;

    bv.putBits(si.main_data_begin, 9);
    if (stereo == 1)
        bv.putBits(si.private_bits, 5);
    else
        bv.putBits(si.private_bits, 3);

    for (ch = 0; ch < stereo; ch++) {
        bv.putBits(si.ch[ch].gr[1].scfsi, 4);
    }

    for (gr = 0; gr < 2; gr++) {
        for (ch = 0; ch < stereo; ch++) {
            MP3SideInfo::gr_info_s_t const &gr_info = si.ch[ch].gr[gr];

            bv.putBits(gr_info.part2_3_length, 12);
            bv.putBits(gr_info.big_values, 9);
            bv.putBits(gr_info.global_gain, 8);
            bv.putBits(gr_info.scalefac_compress, 4);
            bv.put1Bit(gr_info.window_switching_flag);
            if (gr_info.window_switching_flag) {
                bv.putBits(gr_info.block_type, 2);
                bv.put1Bit(gr_info.mixed_block_flag);
                for (i = 0; i < 2; i++)
                    bv.putBits(gr_info.table_select[i], 5);
                for (i = 0; i < 3; i++)
                    bv.putBits(gr_info.subblock_gain[i], 3);
            } else {
                for (i = 0; i < 3; i++)
                    bv.putBits(gr_info.table_select[i], 5);
                bv.putBits(gr_info.region0_count, 4);
                bv.putBits(gr_info.region1_count, 3);
            }

            bv.put1Bit(gr_info.preflag);
            bv.put1Bit(gr_info.scalefac_scale);
            bv.put1Bit(gr_info.count1table_select);
        }
    }
}

static void putSideInfo2(BitVector &bv,
                         MP3SideInfo const &si, Boolean isStereo) {
    int ch, i;
    int stereo = isStereo ? 2 : 1;

    bv.putBits(si.main_data_begin, 8);
    if (stereo == 1)
        bv.put1Bit(si.private_bits);
    else
        bv.putBits(si.private_bits, 2);

    for (ch = 0; ch < stereo; ch++) {
        MP3SideInfo::gr_info_s_t const &gr_info = si.ch[ch].gr[0];

        bv.putBits(gr_info.part2_3_length, 12);
        bv.putBits(gr_info.big_values, 9);
        bv.putBits(gr_info.global_gain, 8);
        bv.putBits(gr_info.scalefac_compress, 9);
        bv.put1Bit(gr_info.window_switching_flag);
        if (gr_info.window_switching_flag) {
            bv.putBits(gr_info.block_type, 2);
            bv.put1Bit(gr_info.mixed_block_flag);
            for (i = 0; i < 2; i++)
                bv.putBits(gr_info.table_select[i], 5);
            for (i = 0; i < 3; i++)
                bv.putBits(gr_info.subblock_gain[i], 3);
        } else {
            for (i = 0; i < 3; i++)
                bv.putBits(gr_info.table_select[i], 5);
            bv.putBits(gr_info.region0_count, 4);
            bv.putBits(gr_info.region1_count, 3);
        }

        bv.put1Bit(gr_info.scalefac_scale);
        bv.put1Bit(gr_info.count1table_select);
    }
}

static void PutMP3SideInfoIntoFrame(MP3SideInfo const &si,
                                    MP3FrameParams const &fr,
                                    unsigned char *framePtr) {
    if (fr.hasCRC) framePtr += 2; // skip CRC

    BitVector bv(framePtr, 0, 8 * fr.sideInfoSize);

    if (fr.isMPEG2) {
        putSideInfo2(bv, si, fr.isStereo);
    } else {
        putSideInfo1(bv, si, fr.isStereo);
    }
}


Boolean ZeroOutMP3SideInfo(unsigned char *framePtr, unsigned totFrameSize,
                           unsigned newBackpointer) {
    if (totFrameSize < 4) return False; // there's not enough data

    MP3FrameParams fr;
    fr.hdr = ((unsigned) framePtr[0] << 24) | ((unsigned) framePtr[1] << 16)
             | ((unsigned) framePtr[2] << 8) | (unsigned) framePtr[3];
    fr.setParamsFromHeader();
    fr.setBytePointer(framePtr + 4, totFrameSize - 4); // skip hdr

    if (totFrameSize < 4 + fr.sideInfoSize) return False; // not enough data

    MP3SideInfo si;
    fr.getSideInfo(si);

    si.main_data_begin = newBackpointer; /* backpointer */
    /* set all four "part2_3_length" and "big_values" fields to zero: */
    si.ch[0].gr[0].part2_3_length = si.ch[0].gr[0].big_values = 0;
    si.ch[1].gr[0].part2_3_length = si.ch[1].gr[0].big_values = 0;
    si.ch[0].gr[1].part2_3_length = si.ch[0].gr[1].big_values = 0;
    si.ch[1].gr[1].part2_3_length = si.ch[1].gr[1].big_values = 0;

    PutMP3SideInfoIntoFrame(si, fr, framePtr + 4);

    return True;
}


static unsigned MP3BitrateToBitrateIndex(unsigned bitrate /* in kbps */,
                                         Boolean isMPEG2) {
    for (unsigned i = 1; i < 15; ++i) {
        if (live_tabsel[isMPEG2][2][i] >= bitrate)
            return i;
    }

    // "bitrate" was larger than any possible, so return the largest possible:
    return 14;
}

static void outputHeader(unsigned char *toPtr, unsigned hdr) {
    toPtr[0] = (unsigned char) (hdr >> 24);
    toPtr[1] = (unsigned char) (hdr >> 16);
    toPtr[2] = (unsigned char) (hdr >> 8);
    toPtr[3] = (unsigned char) (hdr);
}

static void assignADUBackpointer(MP3FrameParams const &fr,
                                 unsigned aduSize,
                                 MP3SideInfo &sideInfo,
                                 unsigned &availableBytesForBackpointer) {
    // Give the ADU as large a backpointer as possible:
    unsigned maxBackpointerSize = fr.isMPEG2 ? 255 : 511;

    unsigned backpointerSize = availableBytesForBackpointer;
    if (backpointerSize > maxBackpointerSize) {
        backpointerSize = maxBackpointerSize;
    }

    // Store the new backpointer now:
    sideInfo.main_data_begin = backpointerSize;

    // Figure out how many bytes are available for the *next* ADU's backpointer:
    availableBytesForBackpointer
            = backpointerSize + fr.frameSize - fr.sideInfoSize;
    if (availableBytesForBackpointer < aduSize) {
        availableBytesForBackpointer = 0;
    } else {
        availableBytesForBackpointer -= aduSize;
    }
}

unsigned TranscodeMP3ADU(unsigned char const *fromPtr, unsigned fromSize,
                         unsigned toBitrate,
                         unsigned char *toPtr, unsigned toMaxSize,
                         unsigned &availableBytesForBackpointer) {
    // Begin by parsing the input ADU's parameters:
    unsigned hdr, inFrameSize, inSideInfoSize, backpointer, inAduSize;
    MP3SideInfo sideInfo;
    if (!GetADUInfoFromMP3Frame(fromPtr, fromSize,
                                hdr, inFrameSize, sideInfo, inSideInfoSize,
                                backpointer, inAduSize)) {
        return 0;
    }
    fromPtr += (4 + inSideInfoSize); // skip to 'main data'

    // Alter the 4-byte MPEG header to reflect the output ADU:
    // (different bitrate; mono; no CRC)
    Boolean isMPEG2 = ((hdr & 0x00080000) == 0);
    unsigned toBitrateIndex = MP3BitrateToBitrateIndex(toBitrate, isMPEG2);
    hdr &= ~0xF000;
    hdr |= (toBitrateIndex << 12); // set bitrate index
    hdr |= 0x10200; // turn on !error-prot and padding bits
    hdr &= ~0xC0;
    hdr |= 0xC0; // set mode to 3 (mono)

    // Set up the rest of the parameters of the new ADU:
    MP3FrameParams outFr;
    outFr.hdr = hdr;
    outFr.setParamsFromHeader();

    // Figure out how big to make the output ADU:
    unsigned inAveAduSize = inFrameSize - inSideInfoSize;
    unsigned outAveAduSize = outFr.frameSize - outFr.sideInfoSize;
    unsigned desiredOutAduSize /*=inAduSize*outAveAduSize/inAveAduSize*/
            = (2 * inAduSize * outAveAduSize + inAveAduSize) / (2 * inAveAduSize);
    // this rounds to the nearest integer

    if (toMaxSize < (4 + outFr.sideInfoSize)) return 0;
    unsigned maxOutAduSize = toMaxSize - (4 + outFr.sideInfoSize);
    if (desiredOutAduSize > maxOutAduSize) {
        desiredOutAduSize = maxOutAduSize;
    }

    // Figure out the new sizes of the various 'part23 lengths',
    // and how much they are truncated:
    unsigned part23Length0a, part23Length0aTruncation;
    unsigned part23Length0b, part23Length0bTruncation;
    unsigned part23Length1a, part23Length1aTruncation;
    unsigned part23Length1b, part23Length1bTruncation;
    unsigned numAduBits
            = updateSideInfoSizes(sideInfo, outFr.isMPEG2,
                                  fromPtr, 8 * desiredOutAduSize,
                                  part23Length0a, part23Length0aTruncation,
                                  part23Length0b, part23Length0bTruncation,
                                  part23Length1a, part23Length1aTruncation,
                                  part23Length1b, part23Length1bTruncation);
#ifdef DEBUG
    fprintf(stderr, "shrinkage %d->%d [(%d,%d),(%d,%d)] (trunc: [(%d,%d),(%d,%d)]) {%d}\n", inAduSize, (numAduBits+7)/8,
              part23Length0a, part23Length0b, part23Length1a, part23Length1b,
              part23Length0aTruncation, part23Length0bTruncation,
              part23Length1aTruncation, part23Length1bTruncation,
              maxOutAduSize);
#endif
    unsigned actualOutAduSize = (numAduBits + 7) / 8;

    // Give the new ADU an appropriate 'backpointer':
    assignADUBackpointer(outFr, actualOutAduSize, sideInfo, availableBytesForBackpointer);

    ///// Now output the new ADU:

    // 4-byte header
    outputHeader(toPtr, hdr);
    toPtr += 4;

    // side info
    PutMP3SideInfoIntoFrame(sideInfo, outFr, toPtr);
    toPtr += outFr.sideInfoSize;

    // 'main data', using the new lengths
    unsigned toBitOffset = 0;
    unsigned fromBitOffset = 0;

    /* rebuild portion 0a: */
    memmove(toPtr, fromPtr, (part23Length0a + 7) / 8);
    toBitOffset += part23Length0a;
    fromBitOffset += part23Length0a + part23Length0aTruncation;

    /* rebuild portion 0b: */
    shiftBits(toPtr, toBitOffset, fromPtr, fromBitOffset, part23Length0b);
    toBitOffset += part23Length0b;
    fromBitOffset += part23Length0b + part23Length0bTruncation;

    /* rebuild portion 1a: */
    shiftBits(toPtr, toBitOffset, fromPtr, fromBitOffset, part23Length1a);
    toBitOffset += part23Length1a;
    fromBitOffset += part23Length1a + part23Length1aTruncation;

    /* rebuild portion 1b: */
    shiftBits(toPtr, toBitOffset, fromPtr, fromBitOffset, part23Length1b);
    toBitOffset += part23Length1b;

    /* zero out any remaining bits (probably unnecessary, but...) */
    unsigned char const zero = '\0';
    shiftBits(toPtr, toBitOffset, &zero, 0,
              actualOutAduSize * 8 - numAduBits);

    return 4 + outFr.sideInfoSize + actualOutAduSize;
}
